<?php

/**
 * Yii Implementation of the Jquery Input token widget.
 *
 * @author Poh Nean
 */
class JqueryTokenInput extends CInputWidget
{

    private $source;
    private $options = array();
    private $callbacks = array();
    private $baseUrl;
    /**
     * Valid options for token input
     */
    protected $validOptions = array(
        'hintText' => array('type' => 'string'),
        'noResultsText' => array('type' => 'string'),
        'searchingText' => array('type' => 'string'),
        'errorMinlengthText' => array('type' => 'string'),
        'errorDuplicateText' => array('type' => 'string'),
        'prePopulate' => array('type' => 'array'),
        'tokenLimit' => array('type' => 'integer'),
        'allowNewTokens' => array('type' => 'boolean'),
        'newTokenMinLength' => array('type' => 'integer'),
        'newTokenInputName' => array('type' => 'string'),
        'selectedTokenInputName' => array('type' => 'string'),
    );
    /**
     * Valid callbacks for token input
     */
    protected $validCallbacks = array(
        'onSelect',
        'onChange',
        'onNewTokenAdded',
        'onDelete',
        'afterPrepopulate',
    );

    public function run()
    {
        // Publish and register assets
        $cs = Yii::app()->getClientScript();
        $dir = dirname(__FILE__) . '/JqueryTokenInput';

        $this->baseUrl = Yii::app()->getAssetManager()->publish($dir);
        $cs->registerCoreScript('jquery');
        $cs->registerCssFile($this->baseUrl . '/css/token-input-facebook.css');
        $cs->registerCssFile($this->baseUrl . '/css/token-input.css');
        $cs->registerScriptFile($this->baseUrl . '/js/jquery.tokeninput.js',
                CClientScript::POS_HEAD);

        list($name, $id) = $this->resolveNameID();
        $options = $this->makeOptions();
        $js = <<<EOP
jQuery('#{$id}').tokenInput('{$this->source}',{$options});
EOP;
        $cs->registerScript('Yii.' . get_class($this) . '#' . $id, $js, CClientScript::POS_READY);

        // Render textfield.
        $validators = $this->model->getValidators($this->attribute);
        if(!empty($validators)) {
            foreach ($validators as $validator) {
                // check if maxlength is set in one of the validators.
                if ($validator instanceof CStringValidator) {
                    $this->htmlOptions["maxlength"] = $validator->max;
                    break;
                }
            }
        }
        
        echo CHtml::textField($name, $this->value, $this->htmlOptions);
    }

    /**
     * From JUI
     * Check callbacks against valid callbacks
     * @param array $value user's callbacks
     * @param array $validCallbacks valid callbacks
     */
    protected static function checkCallbacks($value, $validCallbacks)
    {
        if (!empty($validCallbacks)) {
            foreach ($value as $key => $val)
            {
                if (!in_array($key, $validCallbacks)) {
                    throw new CException(Yii::t('AjaxUploader', '{k} must be one of: {c}', array('{k}' => $key, '{c}' => implode(', ', $validCallbacks))));
                }
            }
        }
    }

    /**
     * From JUI extension
     * Check the options against the valid ones
     *
     * @param array $value user's options
     * @param array $validOptions valid options
     */
    protected static function checkOptions($value, $validOptions)
    {
        if (!empty($validOptions)) {
            foreach ($value as $key => $val)
            {

                if (!array_key_exists($key, $validOptions)) {
                    throw new CException(Yii::t('JqueryTokenInput', '{k} is not a valid option', array('{k}' => $key)));
                }
                $type = gettype($val);
                if ((!is_array($validOptions[$key]['type']) && ($type != $validOptions[$key]['type'])) || (is_array($validOptions[$key]['type']) && !in_array($type, $validOptions[$key]['type']))) {
                    throw new CException(Yii::t('JqueryTokenInput', '{k} must be of type {t}',
                                    array('{k}' => $key, '{t}' => $validOptions[$key]['type'])));
                }
                if (array_key_exists('possibleValues', $validOptions[$key])) {
                    if (!in_array($val, $validOptions[$key]['possibleValues'])) {
                        throw new CException(Yii::t('JqueryTokenInput', '{k} must be one of: {v}', array('{k}' => $key, '{v}' => implode(', ', $validOptions[$key]['possibleValues']))));
                    }
                }
                if (($type == 'array') && array_key_exists('elements', $validOptions[$key])) {
                    self::checkOptions($val, $validOptions[$key]['elements']);
                }
            }
        }
    }

    /**
     * Get the source url.
     * @return string the source url.
     */
    public function getSource()
    {
        return $this->source;
    }

    /**
     * Set the source url.
     * @param string $source
     */
    public function setSource($source)
    {
        $this->source = $source;
    }

    /**
     * Getter
     * @return array
     */
    public function getCallbacks()
    {
        return $this->callbacks;
    }

    /**
     * Setter
     * @param array $value callbacks
     */
    public function setCallbacks($value)
    {
        if (!is_array($value))
            throw new CException(Yii::t('JqueryTokenInput', 'callbacks must be an associative array'));
        self::checkCallbacks($value, $this->validCallbacks);
        $this->callbacks = $value;
    }

    /**
     * Getter
     * @return array
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Setter
     * @param mixed $value
     */
    public function setOptions($value)
    {
        if (!is_array($value))
            throw new CException(Yii::t('JqueryTokenInput', 'options must be an array'));
        self::checkOptions($value, $this->validOptions);
        $this->options = $value;
    }

    /**
     * encode Options & Callbacks
     * @return string
     */
    protected function makeOptions()
    {
        // Set defaults
        if (!array_key_exists('name', $this->options))
            $this->options['name'] = $this->name;
        $this->options = array_merge($this->options, $this->callbacks);

        $encodedOptions = CJavaScript::encode($this->options);
        return $encodedOptions;
    }

}

?>
